﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Notoric.Model.Elements;
using Notoric.Model.Validation;

namespace Notoric.Model.Packaging
{
    /// <summary>
    /// Implements an XML package part that holds the data of the project model element.
    /// </summary>
    public sealed class ProjectPart : XmlPackagePart
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ProjectPart"/> instance.
        /// </summary>
        /// <param name="package">
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The parent package (the package that owns this part).
        /// </para>
        /// </param>
        /// <param name="implementationPart">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackagePart"/>
        /// </para>
        /// <para>
        /// An existing part of the parent's <see cref="Notoric.Model.Packaging.Package.ImplementationPackage"/> that implements
        /// the core functionality of this part.
        /// </para>
        /// </param>
        /// <param name="createNew">
        /// <para>
        /// Type: <see cref="System.Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether the content of this part should be created from scratch or loaded from the part content.<br/>
        /// <see langword="true"/> means create a new part content, <see langword="false"/> means load the content from
        /// the physical storage.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="package"/> or <paramref name="implementationPart"/> is <see langword="null"/>.
        /// </exception>
        internal ProjectPart(Package package, System.IO.Packaging.PackagePart implementationPart, bool createNew)
            : base(package, implementationPart, createNew)
        {
        }

        #endregion

        #region XmlPackagePart Members

        /// <inheritdoc/>
        protected override ModelElement CreateAndLoadRoot(XmlElement element)
        {
            return new ProjectElement(this, element);
        }

        /// <inheritdoc/>
        protected override ModelElement CreateNewRoot()
        {
            return new ProjectElement(this);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates a new <see cref="CompositionPart"/> under this <see cref="ProjectPart"/>.
        /// </summary>
        /// <param name="compositionPath">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The virtual path that will be assigned to this composition part.<br/>
        /// The virtual path is relative to the root compositions directory and starts with a slash "/".
        /// <example>
        /// For a composition named "Art of Fugue" in the "Organ" directory that is a subdirectory of "Bach" directory,
        /// the path will look the following:<br/>
        /// <i>/Bach/Organ/Art of Fugue</i><br/>
        /// </example>
        /// The path can contain spaces.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The created part.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="compositionPath"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="compositionPath"/> is an empty string ("")<br/>
        /// -or-
        /// A composition part with the same path already exists in the package.
        /// </exception>
        public CompositionPart CreateCompositionPart(string compositionPath)
        {
            if (compositionPath == null)
                throw new ArgumentNullException("compositionPath");
            if (compositionPath.Length == 0)
                throw new ArgumentException("Composition path must not be empty!", "compositionPath");

            if (compositionPath[0] != '/')
                compositionPath = "/" + compositionPath;

            if (Project.Compositions.Contains(compositionPath))
                throw new ArgumentException("Another composition with the same name and path already exists!", "compositionPath");

            var part = Package.CreateCompositionPart();
            part.CreateLinearView(Elements.CWNotation.MusicSectionInterior.Create, null);
            Project.Compositions.Add(compositionPath, part.Uri);

            return part;
        }

        /// <summary>
        /// Creates a new <see cref="ResourcePart"/> under this <see cref="ProjectPart"/> and loads it with a given file.
        /// </summary>
        /// <param name="resourcePath">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The virtual path that will be assigned to this resource part.<br/>
        /// The virtual path is relative to the root resources directory and starts with a slash "/".
        /// <example>
        /// For a resource named "Bach.jpg" in the "Authors" directory that is a subdirectory of "Pictures" directory,
        /// the path will look the following:<br/>
        /// <i>/Pictures/Authors/Bach.jpg</i><br/>
        /// </example>
        /// The path can contain spaces.
        /// </para>
        /// </param>
        /// <param name="fileName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name and path of the local file to be loaded into the created part.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="ResourcePart"/>
        /// </para>
        /// <para>
        /// The created part.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="resourcePath"/> or <paramref name="fileName"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="resourcePath"/> is an empty string ("")<br/>
        /// -or-
        /// A resource part with the same path already exists in the package.
        /// </exception>
        /// <exception cref="System.IO.FileNotFoundException">
        /// The file identified by <paramref name="fileName"/> was not found.
        /// </exception>
        public ResourcePart CreateResourcePart(string resourcePath, string fileName)
        {
            if (resourcePath == null)
                throw new ArgumentNullException("resourcePath");
            if (resourcePath.Length == 0)
                throw new ArgumentException("Resource path must not be empty!", "resourcePath");

            if (resourcePath[0] != '/')
                resourcePath = "/" + resourcePath;

            if (Project.Resources.Contains(resourcePath))
                throw new ArgumentException("Another resource with the same name and path already exists!", "resourcePath");

            var part = Package.CreateResourcePart(fileName);
            Project.Resources.Add(resourcePath, part.Uri);

            return part;
        }

        /// <summary>
        /// Creates a new <see cref="ScorePart"/> under this <see cref="ProjectPart"/>.
        /// </summary>
        /// <param name="scorePath">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The virtual path that will be assigned to this score part.<br/>
        /// The virtual path is relative to the root scores directory and starts with a slash "/".
        /// <example>
        /// For a score named "Art of Fugue" in the "Organ" directory that is a subdirectory of "Bach" directory,
        /// the path will look the following:<br/>
        /// <i>/Bach/Organ/Art of Fugue</i><br/>
        /// </example>
        /// The path can contain spaces.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="ScorePart"/>
        /// </para>
        /// <para>
        /// The created part.
        /// </para>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="scorePath"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="scorePath"/> is an empty string ("")<br/>
        /// -or-
        /// A score part with the same path already exists in the package.
        /// </exception>
        public ScorePart CreateScorePart(string scorePath)
        {
            if (scorePath == null)
                throw new ArgumentNullException("scorePath");
            if (scorePath.Length == 0)
                throw new ArgumentException("Score path must not be empty!", "scorePath");
            
            if (scorePath[0] != '/')
                scorePath = "/" + scorePath;
            
            if (Project.Scores.Contains(scorePath))
                throw new ArgumentException("Another score with the same name and path already exists!", "scorePath");

            var part = Package.CreateScorePart();
            Project.Scores.Add(scorePath, part.Uri);

            return part;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets a reference to the project of this part.
        /// </summary>
        public ProjectElement Project
        {
            get { return (ProjectElement)RootElement; }
        }

        #endregion
    }
}
